package com.mock.components;

import com.mock.mapper.MagicApiMapper;
import com.mock.model.entity.MagicApiEntity;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.additional.query.impl.LambdaQueryChainWrapper;
import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.parser.core.models.ParseOptions;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.ssssssss.magicapi.core.config.MagicConfiguration;
import org.ssssssss.magicapi.core.model.ApiInfo;
import org.ssssssss.magicapi.core.model.Group;
import org.ssssssss.magicapi.core.model.Parameter;
import org.ssssssss.magicapi.core.model.Path;
import org.ssssssss.magicapi.core.resource.Resource;
import org.ssssssss.magicapi.core.service.MagicAPIService;
import org.ssssssss.magicapi.core.service.MagicResourceService;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author chen zhuobin
 * @date 2022年08月12日 15:24
 * @decription swagger生成magicApi组件
 * @since
 */
@Slf4j
@Component
public class SwaggerGenerateMagicApi {
    //org.ssssssss.magicapi.core.web
    @Autowired
    private MagicResourceService magicResourceService;
    @Autowired
    private MagicAPIService magicAPIService;
    @Autowired
    private MagicApiMapper magicApiMapper;

    private static final Pattern FILE_NAME_PATTERN = Pattern.compile("^(?!\\.)[\\u4e00-\\u9fa5_a-zA-Z0-9.\\-()]+$");


    public List<String> swagger2MagicApi(String swaggerApiUrl, String serverId, boolean replaceGroup) {
        if (StringUtils.isBlank(swaggerApiUrl)) {
            log.warn("swagger地址错误");
            return null;
        }
        //删除分组 group
        if (replaceGroup) {
            deleteGroup(serverId);
        }

        //增加分组
        newGroup(serverId);

        //增加api
        return newApi(swaggerApiUrl, serverId);
    }


    private List<String> newApi(String swaggerApiUrl, String serviceGroupName) {
        List<String> errorApi = new ArrayList<>();

        getGroupByName(serviceGroupName).ifPresent(subGroup -> {
            ParseOptions parseOptions = new ParseOptions();
            parseOptions.setResolveFully(true);
            SwaggerParseResult result = new OpenAPIParser().readLocation(swaggerApiUrl, null, parseOptions);
            OpenAPI openAPI = result.getOpenAPI();
            openAPI.getPaths().forEach((path, pathItem) -> {

                Operation get = pathItem.getGet();
                if (get != null) {
                    ApiInfo apiInfo = operation2ApiInfo(subGroup, openAPI, path, get, HttpMethod.GET);
                    try {
                        MagicConfiguration.getMagicResourceService().saveFile(apiInfo);
                    } catch (Exception e) {
                        errorApi.add(apiInfo.getName());
                    }
                }

                Operation post = pathItem.getPost();
                if (post != null) {
                    ApiInfo apiInfo = operation2ApiInfo(subGroup, openAPI, path, post, HttpMethod.POST);
                    try {
                        MagicConfiguration.getMagicResourceService().saveFile(apiInfo);
                    } catch (Exception e) {
                        errorApi.add(apiInfo.getName());
                    }
                }

                Operation put = pathItem.getPut();
                if (put != null) {
                    ApiInfo apiInfo = operation2ApiInfo(subGroup, openAPI, path, put, HttpMethod.PUT);
                    try {
                        MagicConfiguration.getMagicResourceService().saveFile(apiInfo);
                    } catch (Exception e) {
                        errorApi.add(apiInfo.getName());
                    }
                }
                Operation delete = pathItem.getDelete();
                if (delete != null) {
                    ApiInfo apiInfo = operation2ApiInfo(subGroup, openAPI, path, delete, HttpMethod.DELETE);
                    try {
                        MagicConfiguration.getMagicResourceService().saveFile(apiInfo);
                    } catch (Exception e) {
                        errorApi.add(apiInfo.getName());
                    }
                }
            });
        });
        return errorApi;
    }

    private ApiInfo operation2ApiInfo(Group subGroup, OpenAPI openAPI, String path, Operation operation, HttpMethod method) {
        ApiInfo apiInfo = new ApiInfo();
        apiInfo.setMethod(method.name());

        //设置请求参数
        if (CollectionUtils.isNotEmpty(operation.getParameters())) {
            List<Parameter> parameters = operation.getParameters().stream()
                    .filter(optParameter -> "query".equals(optParameter.getIn()))
                    .map(optParameter -> new Parameter(optParameter.getName(), StringUtils.EMPTY))
                    .collect(Collectors.toList());
            apiInfo.setParameters(parameters);
        }

        apiInfo.setHeaders(null);

        //设置请求体
        String requestBody = Optional.ofNullable(operation.getRequestBody())
                .map(RequestBody::getContent)
                .map(content -> content.get(MediaType.APPLICATION_JSON_VALUE))
                .map(mediaType -> mediaType.getSchema())
                .map(schema -> {
                    HashMap<String, Object> objectMap = new HashMap<>();
                    schemaToMap(schema, objectMap);
                    return JSON.toJSONString(objectMap);
                }).orElse(null);
        apiInfo.setRequestBody(requestBody);

        //设置路径参数
        if (CollectionUtils.isNotEmpty(operation.getParameters())) {
            List<Path> paths = operation.getParameters().stream()
                    .filter(optParameter -> "path".equals(optParameter.getIn()))
                    .map(optParameter -> new Path(optParameter.getName(), StringUtils.EMPTY))
                    .collect(Collectors.toList());
            apiInfo.setPaths(paths);
        }


        Map<String, Object> params = new HashMap<>();
        params.put("data", scriptData(operation, path));
        //设置响应内容
        String scriptStr = magicAPIService.invoke("/magicApi/template", params);
        apiInfo.setScript(scriptStr);


        apiInfo.setPath(path);
        apiInfo.setId(UUID.randomUUID().toString().replace("-", ""));
        apiInfo.setGroupId(subGroup.getId());

        String summary = operation.getSummary();
        apiInfo.setDescription(summary);

        String name = path2Name(path, method);
        apiInfo.setName(name);
        return apiInfo;
    }

    private void schemaToMap(Schema schema, Map<String, Object> objectMap) {
        if (schema.getProperties() == null) {
            return;
        }
        schema.getProperties().forEach((field, fieldSchema) -> {
            Schema<?> fieldSchemaObj = (Schema<?>) fieldSchema;
            String type = fieldSchemaObj.getType();
            switch (type) {
                case "array":
                    List<Map<String, java.lang.Object>> array = new ArrayList<>();
                    Schema<?> itemsSchema = fieldSchemaObj.getItems();
                    Map<String, Object> itemsMap = new HashMap<>();
                    schemaToMap(itemsSchema, itemsMap);
                    if (!itemsMap.isEmpty()) {
                        array.add(itemsMap);
                    }
                    objectMap.put(field.toString(), array);
                    break;
                case "string":
                    objectMap.put(field.toString(), null);
                    break;
                case "boolean":
                    objectMap.put(field.toString(), null);
                    break;
                case "integer":
                    objectMap.put(field.toString(), null);
                    break;
                case "object":
                    Map<String, Object> propertiesMap = new HashMap<>();
                    schemaToMap(fieldSchemaObj, propertiesMap);
                    if (!propertiesMap.isEmpty()) {
                        objectMap.put(field.toString(), propertiesMap);
                    } else {
                        objectMap.put(field.toString(), null);
                    }
                    break;
                case "number":
                    objectMap.put(field.toString(), null);
                    break;
                default:
                    objectMap.put(field.toString(), null);
                    break;
            }
        });
    }

    private Map<String, Object> scriptData(Operation operation, String path) {
        HashMap<String, Object> scriptData = new HashMap<>();
        String scriptDataStr = Optional.ofNullable(operation.getResponses())
                .map(apiResponses -> apiResponses.get("200"))
                .map(ApiResponse::getContent)
                .map(content -> content.get("*/*"))
                .map(mediaType -> mediaType.getSchema())
                .map(schema -> {

                    HashMap<String, String> responseMap = new HashMap<>();
                    objectSchemaToMocker(schema, responseMap);
                    responseMap.put("code", "0");
                    responseMap.put("path", "'" + path + "'");
                    responseMap.put("httpStatus", "200");
                    responseMap.put("timestamp", "System.currentTimeMillis()");
                    responseMap.put("message", "'success'");
                    return responseMap.toString().replaceAll("=", ":");
                }).orElseGet(() -> {
                    HashMap<String, String> responseMap = new HashMap<>();
                    responseMap.put("code", "0");
                    responseMap.put("path", "'" + path + "'");
                    responseMap.put("httpStatus", "200");
                    responseMap.put("timestamp", "System.currentTimeMillis()");
                    responseMap.put("data", null);
                    responseMap.put("extra", null);
                    responseMap.put("message", "'success'");
                    return responseMap.toString().replaceAll("=", ":");
                });

        scriptData.put("mockData", scriptDataStr);
        return scriptData;
    }


    private void objectSchemaToMocker(Schema schema, Map<String, String> objectMap) {
        if ("object".equals(schema.getType())) {
            if (schema.getProperties() == null) {
                return;
            }
            schema.getProperties().forEach((field, fieldSchema) -> {
                Schema<?> fieldSchemaObj = (Schema<?>) fieldSchema;
                String type = fieldSchemaObj.getType();
                switch (type) {
                    case "array":
                        List<Map<String, String>> array = new ArrayList<>();
                        Schema<?> itemsSchema = fieldSchemaObj.getItems();
                        Map<String, String> itemsMap = new HashMap<>();
                        objectSchemaToMocker(itemsSchema, itemsMap);
                        if (!itemsMap.isEmpty()) {
                            array.add(itemsMap);
                        }
                        objectMap.put(field.toString(), array.toString());
                        break;
                    case "string":
                        objectMap.put(field.toString(), "JMockData.mock(String.class)");
                        break;
                    case "boolean":
                        objectMap.put(field.toString(), "JMockData.mock(Boolean.class)");
                        break;
                    case "integer":
                        objectMap.put(field.toString(), "JMockData.mock(Integer.class)");
                        break;
                    case "object":
                        Map<String, String> propertiesMap = new HashMap<>();
                        objectSchemaToMocker(fieldSchemaObj, propertiesMap);
                        if (!propertiesMap.isEmpty()) {
                            objectMap.put(field.toString(), propertiesMap.toString());
                        } else {
                            objectMap.put(field.toString(), null);
                        }
                        break;
                    case "number":
                        objectMap.put(field.toString(), "JMockData.mock(Integer.class)");
                        break;
                    default:
                        objectMap.put(field.toString(), null);
                        break;
                }
            });
        }
        return;
    }

    private void deleteGroup(String serviceGroupName) {
        getGroupByName(serviceGroupName).ifPresent(subGroup -> magicResourceService.delete(subGroup.getId()));
    }

    private void newGroup(String serviceGroupName) {
        Group apiGroup = new Group();
        apiGroup.setType("api");
        apiGroup.setParentId("0");
        apiGroup.setName(serviceGroupName);
        magicResourceService.saveGroup(apiGroup);
    }

    private Optional<Group> getGroupByName(String serviceGroupName) {
        Optional<Resource> groupRes = magicResourceService.getResource().getDirectory("api").dirs().stream().filter(subGroup -> serviceGroupName.equals(subGroup.name())).findFirst();

        return groupRes.map(resource -> {
            String absolutePath = resource.getAbsolutePath();
            String absolutePathJsonPath = absolutePath + "group.json";
            MagicApiEntity magicApiEntity = new LambdaQueryChainWrapper<>(magicApiMapper).eq(MagicApiEntity::getFilePath, absolutePathJsonPath).one();
            String fileContent = magicApiEntity.getFileContent();
            Group group = JSON.parseObject(fileContent, Group.class);
            return magicResourceService.getGroup(group.getId());
        });
    }


    private String path2Name(String path, HttpMethod method) {
        String name = StringUtils.replace(path, "/", ".");
        name = StringUtils.replace(name, "{", "");
        name = StringUtils.replace(name, "}", "");
        if (name.startsWith(".")) {
            name = name.replaceFirst("\\.", "");
        }
        name = method.name() + "(" + name + ")";
        return name;
    }

}
